package vh.android.multiconnector.library;

import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Random;

import vh.android.multiconnector.library.model.ServerListener;
import android.util.Log;

/**
 * Basically this is a {@link ServerSocket} with something like a user
 * managment.
 * 
 * @author Robert Hahn
 * 
 */
public class Server implements Runnable {

	private static final String LOG_TAG = Server.class.getName();
	private static final int SERVER_PORT_RANGE_START = 1500;
	private static final int SERVER_PORT_RANGE_END = 15000;
	private static final int TIMEOUT = 10000;
	private static final int MAX_TIMEOUT = 25000;

	private ServerSocket mServer;
	private int mPort;
	private boolean mRunning;
	private Thread mThread;
	ArrayList<ConnectionHandler> connections = new ArrayList<ConnectionHandler>();
	private ServerListener mListener;

	public Server(ServerListener pListener) {
		this.mListener = pListener;
	}

	/**
	 * Exceuted in a background thread. Will start a new {@link ServerSocket} -
	 * if there isnt one running already.
	 * 
	 */
	private void createSocket() {
		if (mServer != null) {
			Log.e(LOG_TAG,
					"Server already started. Close the old one before creating a new one.");
			return;
		}
		mServer = null;
		while (true) {
			mPort = getRandomPort();
			try {
				mServer = new ServerSocket(mPort);
				mListener.onServerCreated(mServer, mPort);
				Log.d(LOG_TAG, "Server opend at port " + mPort);
				break;
			} catch (IOException e) {
				if (e.getClass().equals(BindException.class)) {
					// port already in use
					Log.e(LOG_TAG, "Port " + mPort + " already in use.");
				} else {
					// another exception
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * whether service is running
	 * 
	 * @return
	 */
	public boolean isRunning() {
		return mRunning;
	}

	/**
	 * start unless already running
	 */
	public void start() {
		if (!mRunning) {
			mRunning = true;
			mThread = new Thread(this);
			mThread.start();
		}
	}

	public void stop() {
		mRunning = false;
	}

	public ServerSocket getSocket() {
		return mServer;
	}

	public int getPort() {
		return mPort;
	}

	private static int getRandomPort() {
		Random random = new Random(System.currentTimeMillis());
		int port = random.nextInt(SERVER_PORT_RANGE_END
				- SERVER_PORT_RANGE_START);
		port += SERVER_PORT_RANGE_START;
		return port;
	}

	@Override
	public void run() {
		// create server socket if not dont yet
		createSocket();

		// TODO use !Thread.currentThread().isInterrupted() instead of running
		while (mRunning) {
			try {
				Socket client = mServer.accept();
				client.setSoTimeout(TIMEOUT);
				
				mListener.onClientJoined(client);
				Log.d(LOG_TAG, "New client accepted. "
						+ client.getInetAddress().toString());

				ConnectionHandler conection = new ConnectionHandler("server", mListener, client);
				conection.start();

				synchronized (connections) {
					connections.add(conection);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}
